using System;
using System.Runtime.InteropServices;

using Gtk;
using Gdk;

namespace Egg
{
    public class TrayIcon : Plug
    {
        int stamp;
        Orientation orientation;

        int selection_atom;
        int manager_atom;
        int system_tray_opcode_atom;
        int orientation_atom;
        IntPtr manager_window;
        FilterFunc filter;

        public TrayIcon (string name)
        {
            Title = name;
            stamp = 1;
            orientation = Orientation.Horizontal;
            AddEvents ((int)EventMask.PropertyChangeMask);
            filter = new FilterFunc (ManagerFilter);
        }

        protected override void OnRealized ()
        {
            base.OnRealized ();
            Display display = Screen.Display;
            IntPtr xdisplay = gdk_x11_display_get_xdisplay (display.Handle);
            selection_atom = XInternAtom (xdisplay, "_NET_SYSTEM_TRAY_S" + Screen.Number.ToString (), false);
            manager_atom = XInternAtom (xdisplay, "MANAGER", false);
            system_tray_opcode_atom = XInternAtom (xdisplay, "_NET_SYSTEM_TRAY_OPCODE", false);
            orientation_atom = XInternAtom (xdisplay, "_NET_SYSTEM_TRAY_ORIENTATION", false);
            UpdateManagerWindow ();
            //Screen.RootWindow.AddFilter (filter);
        }

        protected override void OnUnrealized ()
        {
            if (manager_window != IntPtr.Zero)
            {
                Gdk.Window gdkwin = Gdk.Window.LookupForDisplay (Display, (uint)manager_window);
                //gdkwin.RemoveFilter (filter);
            }

            //Screen.RootWindow.RemoveFilter (filter);
            base.OnUnrealized ();
        }

        private void UpdateManagerWindow ()
        {
            IntPtr xdisplay = gdk_x11_display_get_xdisplay (Display.Handle);
            if (manager_window != IntPtr.Zero)
            {
                Gdk.Window gdkwin = Gdk.Window.LookupForDisplay (Display, (uint)manager_window);
                //gdkwin.RemoveFilter (filter);
            }

            XGrabServer (xdisplay);

            manager_window = XGetSelectionOwner (xdisplay, selection_atom);
            if (manager_window != IntPtr.Zero)
                XSelectInput (xdisplay, manager_window, EventMask.StructureNotifyMask | EventMask.PropertyChangeMask);
            XUngrabServer (xdisplay);
            XFlush (xdisplay);

            if (manager_window != IntPtr.Zero) {
                Gdk.Window gdkwin = Gdk.Window.LookupForDisplay (Display, (uint)manager_window);
                //gdkwin.AddFilter (filter);
                SendDockRequest ();
                GetOrientationProperty ();
            }
        }

        private void SendDockRequest ()
        {
            SendManagerMessage (SystemTrayMessage.RequestDock, manager_window, Id, 0, 0);
        }

        private void SendManagerMessage (SystemTrayMessage message, IntPtr window, uint data1, uint data2, uint data3)
        {
            XClientMessageEvent ev = new XClientMessageEvent ();
            IntPtr display;

            ev.type = XEventName.ClientMessage;
            ev.window = window;
            ev.message_type = (IntPtr)system_tray_opcode_atom;
            ev.format = 32;
            ev.ptr1 = gdk_x11_get_server_time (GdkWindow.Handle);
            ev.ptr2 = (IntPtr)message;
            ev.ptr3 = (IntPtr)data1;
            ev.ptr4 = (IntPtr)data2;
            ev.ptr5 = (IntPtr)data3;

            display = gdk_x11_display_get_xdisplay (Display.Handle);
            gdk_error_trap_push ();
            XSendEvent (display, manager_window, false, EventMask.NoEventMask, ref ev);
            gdk_error_trap_pop ();
        }

        private FilterReturn ManagerFilter (IntPtr xevent, Event evnt)
        {
            //TODO: Implement;
            return FilterReturn.Continue;
        }

        private void GetOrientationProperty ()
        {
            //TODO: Implement;
        }

        [DllImport ("gdk-x11-2.0")]
        static extern IntPtr gdk_x11_display_get_xdisplay (IntPtr display);
        [DllImport ("gdk-x11-2.0")]
        static extern IntPtr gdk_x11_get_server_time (IntPtr window);
        [DllImport ("gdk-x11-2.0")]
        static extern void gdk_error_trap_push ();
        [DllImport ("gdk-x11-2.0")]
        static extern void gdk_error_trap_pop ();

        [DllImport ("libX11", EntryPoint="XInternAtom")]
        extern static int XInternAtom(IntPtr display, string atom_name, bool only_if_exists);
        [DllImport ("libX11")]
        extern static void XGrabServer (IntPtr display);
        [DllImport ("libX11")]
        extern static void XUngrabServer (IntPtr display);
        [DllImport ("libX11")]
        extern static int XFlush (IntPtr display);
        [DllImport ("libX11")]
        extern static IntPtr XGetSelectionOwner (IntPtr display, int atom);
        [DllImport ("libX11")]
        extern static IntPtr XSelectInput (IntPtr window, IntPtr display, EventMask mask);
        [DllImport ("libX11", EntryPoint="XSendEvent")]
        extern static int XSendEvent(IntPtr display, IntPtr window, bool propagate, EventMask event_mask, ref XClientMessageEvent send_event);
    }

    [Flags]
    internal enum EventMask {
        NoEventMask             = 0,
        KeyPressMask            = 1<<0,
        KeyReleaseMask          = 1<<1,
        ButtonPressMask         = 1<<2,
        ButtonReleaseMask       = 1<<3,
        EnterWindowMask         = 1<<4,
        LeaveWindowMask         = 1<<5,
        PointerMotionMask       = 1<<6,
        PointerMotionHintMask   = 1<<7,
        Button1MotionMask       = 1<<8,
        Button2MotionMask       = 1<<9,
        Button3MotionMask       = 1<<10,
        Button4MotionMask       = 1<<11,
        Button5MotionMask       = 1<<12,
        ButtonMotionMask        = 1<<13,
        KeymapStateMask         = 1<<14,
        ExposureMask            = 1<<15,
        VisibilityChangeMask    = 1<<16,
        StructureNotifyMask     = 1<<17,
        ResizeRedirectMask      = 1<<18,
        SubstructureNotifyMask  = 1<<19,
        SubstructureRedirectMask= 1<<20,
        FocusChangeMask         = 1<<21,
        PropertyChangeMask      = 1<<22,
        ColormapChangeMask      = 1<<23,
        OwnerGrabButtonMask     = 1<<24
    }

    internal enum SystemTrayMessage
    {
        RequestDock,
        BeginMessage,
        CancelMessage
    }

    internal enum SystemTrayOrientation
    {
        Horz,
        Vert
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct XClientMessageEvent {
        internal XEventName     type;
        internal int            serial;
        internal bool           send_event;
        internal IntPtr         display;
        internal IntPtr         window;
        internal IntPtr         message_type;
        internal int            format;
        internal IntPtr         ptr1;
        internal IntPtr         ptr2;
        internal IntPtr         ptr3;
        internal IntPtr         ptr4;
        internal IntPtr         ptr5;
    }

    internal enum XEventName {
        KeyPress                = 2,
        KeyRelease              = 3,
        ButtonPress             = 4,
        ButtonRelease           = 5,
        MotionNotify            = 6,
        EnterNotify             = 7,
        LeaveNotify             = 8,
        FocusIn                 = 9,
        FocusOut                = 10,
        KeymapNotify            = 11,
        Expose                  = 12,
        GraphicsExpose          = 13,
        NoExpose                = 14,
        VisibilityNotify        = 15,
        CreateNotify            = 16,
        DestroyNotify           = 17,
        UnmapNotify             = 18,
        MapNotify               = 19,
        MapRequest              = 20,
        ReparentNotify          = 21,
        ConfigureNotify         = 22,
        ConfigureRequest        = 23,
        GravityNotify           = 24,
        ResizeRequest           = 25,
        CirculateNotify         = 26,
        CirculateRequest        = 27,
        PropertyNotify          = 28,
        SelectionClear          = 29,
        SelectionRequest        = 30,
        SelectionNotify         = 31,
        ColormapNotify          = 32,
        ClientMessage           = 33,
        MappingNotify           = 34,
        TimerNotify             = 100,

        LASTEvent
    }
}

